library(rental)
library(cancensus)
Listings Overview
As an example we read the August data for unfurnished listings for Vancouver, Calgary and Toronto.
region_names=c("Vancouver","Calgary","Toronto")
regions=list_census_regions('CA16') %>% filter(level=="CSD",name %in% region_names)
Querying CensusMapper API for regions data...
geo=get_census(dataset = 'CA16',regions=as_census_region_list(regions),geo_format='sf',level="Regions")
Reading geo data from local cache.
for (i in 1:nrow(geo)) {
row=geo[i,]
ls <- get_listings("2017-08-01","2017-09-01",row$geometry,filter = 'unfurnished')
summary=ls %>% as.data.frame %>% select("price","beds") %>% group_by(beds) %>% summarize(median=paste0("$",format(median(price),big.mark=","))) %>% mutate(name=row$name)
print(row$name)
print(summary)
#print(paste0(row$name," median price: $",format(median(ls$price),big.mark=",")))
}
[1] "Toronto (C)"
[1] "Calgary (CY)"
[1] "Vancouver (CY)"
Map
library(sf)
library(ggplot2)
geo=get_census(dataset = 'CA16',regions=list(CMA="59933"),geo_format='sf',level="Regions")
ls <- get_listings("2017-08-01","2017-09-01",geo$geometry,beds=c(1),filter = 'unfurnished')
summary=ls %>% as.data.frame %>% select("price","beds") %>% group_by(beds) %>% summarize(median=paste0("$",format(median(price),big.mark=","))) %>% mutate(name=row$name)
cts=get_census(dataset = 'CA16',regions=list(CMA="59933"),geo_format='sf',level="CSD")
min_listings=10
median_rent <- function(v){
result <- if (length(v)>min_listings) {
median(v)
} else {
NA
}
return(result)
}
aggregate_listings <- aggregate(cts %>% select("GeoUID"),ls,function(x){x})
data <- aggregate(ls %>% select("price"),cts,median_rent)
cutoffs=as.integer(quantile(data$price, probs=seq(0,1,0.1), na.rm=TRUE))
labels=factor(as.character(seq(1,length(cutoffs)-1) %>% lapply(function(i){return(paste0(cutoffs[i]," - ",cutoffs[i+1]))})),order=TRUE)
colors=setNames(RColorBrewer::brewer.pal(length(labels),"RdYlBu"),labels)
data$discrete_price= data$price %>% cut(breaks=cutoffs, labels=labels)
ggplot() +
geom_sf(data=cts, fill="#808080", size=0.1) +
geom_sf(data=data, aes(fill = discrete_price), size=0.1) +
scale_fill_brewer(palette='RdYlBu', direction=-1, na.value="#808080",name="Median Price") +
labs(title="August Studio and 1 Bedroom Unfurnished Median Ask") +
theme_opts

Rent distributions by municipality
library(ggbeeswarm)
library(gridExtra)
regions=as_census_region_list(search_census_regions("Vancouver",'CA16','CMA'))
geo=get_census(dataset = 'CA16',regions=regions,geo_format='sf',level="Regions")
ls <- get_listings("2017-08-01","2017-09-01",geo$geometry,beds=c(1),filter = 'unfurnished',sanity=c(400,4000))
summary=ls %>% as.data.frame %>% select("price","beds") %>% group_by(beds) %>% summarize(median=paste0("$",format(median(price),big.mark=","))) %>% mutate(name=row$name)
geos=get_census(dataset = 'CA16',regions=regions,geo_format='sf',level="CSD") %>%
st_join(ls)
top_munis <- geos %>% group_by(name) %>% summarize(count=length(name)) %>%
top_n(5,count) %>% pull("name")
plot_data <- geos %>% filter(name %in% top_munis) %>%
rename(Municipality=name)
title="Distribution of Unfurnished 1br Rents, August 2017"
p1 <- ggplot(plot_data) +
geom_density(aes(x=price, color=Municipality)) +
labs(title=title)
p2 <- ggplot(plot_data, aes(Municipality, price))+
geom_violin(aes(fill=Municipality )) +
#geom_beeswarm(pch = 1, col='white', cex=0.8, alpha=0.6) +
labs(title=title)
grid.arrange(p1, p2, ncol=1)

Looking into Coquitlam
region_name="Coquitlam" #"Richmond"
regions=as_census_region_list(search_census_regions(region_name,'CA16','CSD') %>% filter(name==region_name))
Querying CensusMapper API for regions data...
geo=get_census(dataset = 'CA16',regions=regions,geo_format='sf',level="Regions")
Reading geo data from local cache.
ls <- get_listings("2017-08-01","2017-09-01",geo$geometry,beds=c(1),filter = 'unfurnished',sanity=c(400,4000))
summary=ls %>% as.data.frame %>%
select("price","beds") %>%
group_by(beds) %>%
summarize(median=paste0("$",format(median(price),big.mark=","))) %>%
mutate(name=row$name)
cutoffs=c(400,1350,4000)
labels=factor(as.character(seq(1,length(cutoffs)-1) %>% lapply(function(i){return(paste0(cutoffs[i]," - ",cutoffs[i+1]))})),order=TRUE)
colors=setNames(c("turquoise","purple"),labels)
ls$discrete_price= ls$price %>% cut(breaks=cutoffs, labels=labels)
#ls <- cbind(ls,st_coordinates(st_transform(ls,102002)$location))
ls <- cbind(ls,st_coordinates(ls$location))
library(ggmap)
Google Maps API Terms of Service: http://developers.google.com/maps/terms.
Please cite ggmap if you use it: see citation('ggmap') for details.
base <- get_map(paste0(region_name,", Canada"), zoom=12, source = "stamen", maptype = "toner",
crop = T)
Map from URL : http://maps.googleapis.com/maps/api/staticmap?center=Coquitlam,+Canada&zoom=12&size=640x640&scale=2&maptype=terrain&sensor=false
Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address=Coquitlam,%20Canada&sensor=false
Map from URL : http://tile.stamen.com/toner/12/649/1400.png
Map from URL : http://tile.stamen.com/toner/12/650/1400.png
Map from URL : http://tile.stamen.com/toner/12/651/1400.png
Map from URL : http://tile.stamen.com/toner/12/652/1400.png
Map from URL : http://tile.stamen.com/toner/12/649/1401.png
Map from URL : http://tile.stamen.com/toner/12/650/1401.png
Map from URL : http://tile.stamen.com/toner/12/651/1401.png
Map from URL : http://tile.stamen.com/toner/12/652/1401.png
Map from URL : http://tile.stamen.com/toner/12/649/1402.png
Map from URL : http://tile.stamen.com/toner/12/650/1402.png
Map from URL : http://tile.stamen.com/toner/12/651/1402.png
Map from URL : http://tile.stamen.com/toner/12/652/1402.png
#ggplot() +
ggmap(base) +
#geom_sf(data=geo, fill="#808080", size=0.1) +
#coord_sf(crs=st_crs(102002)) +
geom_point(data=ls , aes(color = discrete_price, x=X, y=Y), shape=21, size=2) +
scale_fill_manual(palette=colors) +
labs(title="August Studio and 1 Bedroom Unfurnished Median Ask",color="Price") +
theme_opts

Rent distributions over time
region_name="Vancouver"
regions=as_census_region_list(search_census_regions(region_name,'CA16','CSD') %>% filter(name==region_name))
geo=get_census(dataset = 'CA16',regions=regions,geo_format='sf',level="Regions")
ls <- get_listings("2017-05-01","2017-09-01",geo$geometry,beds=c(1),filter = 'unfurnished',sanity=c(400,4000))
ls$year_month <- factor(substr(ls$post_date,0,7),ordered = TRUE)
#ls$year_month_day <- factor(substr(ls$post_date,0,10),ordered = TRUE)
#ls %>% group_by(year_month) %>% summarize(count=length(year_month)) %>% as.data.frame %>% select("year_month","count")
#ls %>% group_by(year_month_day) %>% summarize(count=length(year_month_day)) %>% as.data.frame %>% select("year_month_day","count")
plot_data <- ls %>% as.data.frame %>% select("price","year_month")
title="Distribution of Unfurnished 1br Rents, City of Vancouver"
p1 <- ggplot(plot_data) +
geom_density(aes(x=price, color=year_month)) +
labs(title=title, color="Year-Month")
p2 <- ggplot(plot_data, aes(year_month, price))+
geom_violin(aes(fill=year_month )) +
#geom_beeswarm(pch = 1, col='white', cex=0.8, alpha=0.6) +
labs(title=title, fill="Year-Month", x="Year-Month")
grid.arrange(p1, p2, ncol=1)

Rent distributions by municipality
library(ggbeeswarm)
library(gridExtra)
region_names=c("Vancouver","Toronto","Victoria","Calgary")
regions= as_census_region_list(do.call(rbind,lapply(region_names,function(region_name){return((search_census_regions(region_name,'CA16','CSD') %>% filter(name==region_name)))})))
geo=get_census(dataset = 'CA16',regions=regions,geo_format='sf',level="Regions")
geometry=st_union(geo$geometry)
beds=2
ls <- get_listings("2017-08-01","2017-09-01",geometry,beds=c(beds),filter = 'unfurnished',sanity=c(400,5000))
summary=ls %>% as.data.frame %>% select("price","beds") %>% group_by(beds) %>% summarize(median=paste0("$",format(median(price),big.mark=","))) %>% mutate(name=row$name)
geos=get_census(dataset = 'CA16',regions=regions,geo_format='sf',level="CSD") %>%
st_join(ls)
plot_data <- geos %>% as.data.frame %>% select("name","price") %>%
rename(Municipality=name)
title=paste0("Distribution of Unfurnished ",beds,"br Rents, August 2017")
p1 <- ggplot(plot_data) +
geom_density(aes(x=price, color=Municipality)) +
labs(title=title)
p2 <- ggplot(plot_data, aes(Municipality, price))+
geom_violin(aes(fill=Municipality )) +
#geom_beeswarm(pch = 1, col='white', cex=0.8, alpha=0.6) +
labs(title=title)
grid.arrange(p1, p2, ncol=1)

Checking Specific area
geo=sf::read_sf("../data/custom_region.geojson")
ls <- get_listings("2017-06-01","2017-09-01",geo$geometry,beds=c(1),filter = 'unfurnished')
summary=ls %>% as.data.frame %>% select("price","beds") %>% group_by(beds) %>% summarize(median=paste0("$",format(median(price),big.mark=","))) %>% mutate(name=row$name)
ggplot(ls, aes(1, price))+
geom_violin(aes(fill=1 )) +
#geom_beeswarm(pch = 1, col='white', cex=0.8, alpha=0.6) +
labs(title="June-August 1br unfurnished Custom Region")

NA
cutoffs=c(400,1050,4000)
labels=factor(as.character(seq(1,length(cutoffs)-1) %>% lapply(function(i){return(paste0(cutoffs[i]," - ",cutoffs[i+1]))})),order=TRUE)
colors=setNames(c("turquoise","purple"),labels)
ls$discrete_price= ls$price %>% cut(breaks=cutoffs, labels=labels)
ls <- cbind(ls,st_coordinates(ls$location))
base <- get_map(location=c(-122.84594535827637, 49.18422801616818), zoom=15, source = "stamen", maptype = "toner",
crop = T)
Map from URL : http://maps.googleapis.com/maps/api/staticmap?center=49.184228,-122.845945&zoom=15&size=640x640&scale=2&maptype=terrain&sensor=false
Map from URL : http://tile.stamen.com/toner/15/5201/11226.png
cannot remove file '28c855ee15d7269ce1ee40976aafb2c9.rds', reason 'No such file or directory'Map from URL : http://tile.stamen.com/toner/15/5202/11226.png
cannot remove file '2598bf38af25fdc54f076106846b96db.rds', reason 'No such file or directory'Map from URL : http://tile.stamen.com/toner/15/5203/11226.png
cannot remove file 'd6a6d7d85bf368c76d4b9009e2e37015.rds', reason 'No such file or directory'Map from URL : http://tile.stamen.com/toner/15/5201/11227.png
cannot remove file '6a8ae25abb70c82bf392746ca7c34f9e.rds', reason 'No such file or directory'Map from URL : http://tile.stamen.com/toner/15/5202/11227.png
cannot remove file '11614fb878fa36990dcc2cf2bc14d9f8.rds', reason 'No such file or directory'Map from URL : http://tile.stamen.com/toner/15/5203/11227.png
cannot remove file 'f4fcb4e105db6efc4a8a8e426fee3c27.rds', reason 'No such file or directory'Map from URL : http://tile.stamen.com/toner/15/5201/11228.png
cannot remove file '72b54857ef02c5a5bfc0f4e4e2a26de9.rds', reason 'No such file or directory'Map from URL : http://tile.stamen.com/toner/15/5202/11228.png
cannot remove file 'cb645223608cbec9552a1ae29c81783a.rds', reason 'No such file or directory'Map from URL : http://tile.stamen.com/toner/15/5203/11228.png
cannot remove file '0aad992ed9a91c01da43c7bc1af86b93.rds', reason 'No such file or directory'
#ggplot() +
ggmap(base) +
#geom_sf(data=geo, fill="#808080", size=0.1) +
#coord_sf(crs=st_crs(102002)) +
geom_point(data=ls , aes(color = discrete_price, x=X, y=Y), shape=21, size=2) +
scale_fill_manual(palette=colors) +
labs(title="August 1 Bedroom Unfurnished Median Ask",color="Price") +
theme_opts

LS0tCnRpdGxlOiAiUmVudGFsIExpc3RpbmdzIERlbW8iCmF1dGhvcjogIkplbnMgdm9uIEJlcmdtYW5uIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OiBkZWZhdWx0CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAp2aWduZXR0ZTogPgogICVcVmlnbmV0dGVJbmRleEVudHJ5e1JlbnRhbCBMaXN0aW5ncyBEZW1vfQogICVcVmlnbmV0dGVFbmdpbmV7a25pdHI6OnJtYXJrZG93bn0KICAlXFZpZ25ldHRlRW5jb2Rpbmd7VVRGLTh9Ci0tLQoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkocmVudGFsKQpsaWJyYXJ5KGNhbmNlbnN1cykKYGBgCgoKIyMgTGlzdGluZ3MgT3ZlcnZpZXcKQXMgYW4gZXhhbXBsZSB3ZSByZWFkIHRoZSBBdWd1c3QgZGF0YSBmb3IgdW5mdXJuaXNoZWQgbGlzdGluZ3MgZm9yIFZhbmNvdXZlciwgQ2FsZ2FyeSBhbmQgVG9yb250by4KYGBge3J9CgpyZWdpb25fbmFtZXM9YygiVmFuY291dmVyIiwiQ2FsZ2FyeSIsIlRvcm9udG8iKQoKcmVnaW9ucz1saXN0X2NlbnN1c19yZWdpb25zKCdDQTE2JykgJT4lIGZpbHRlcihsZXZlbD09IkNTRCIsbmFtZSAlaW4lIHJlZ2lvbl9uYW1lcykKCmdlbz1nZXRfY2Vuc3VzKGRhdGFzZXQgPSAnQ0ExNicscmVnaW9ucz1hc19jZW5zdXNfcmVnaW9uX2xpc3QocmVnaW9ucyksZ2VvX2Zvcm1hdD0nc2YnLGxldmVsPSJSZWdpb25zIikKCmZvciAoaSBpbiAxOm5yb3coZ2VvKSkgewogIHJvdz1nZW9baSxdCiAgbHMgPC0gZ2V0X2xpc3RpbmdzKCIyMDE3LTA4LTAxIiwiMjAxNy0wOS0wMSIscm93JGdlb21ldHJ5LGZpbHRlciA9ICd1bmZ1cm5pc2hlZCcpCiAgc3VtbWFyeT1scyAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgc2VsZWN0KCJwcmljZSIsImJlZHMiKSAlPiUgZ3JvdXBfYnkoYmVkcykgJT4lIHN1bW1hcml6ZShtZWRpYW49cGFzdGUwKCIkIixmb3JtYXQobWVkaWFuKHByaWNlKSxiaWcubWFyaz0iLCIpKSkgJT4lIG11dGF0ZShuYW1lPXJvdyRuYW1lKQogIHByaW50KHJvdyRuYW1lKQogIHByaW50KHN1bW1hcnkpCiAgI3ByaW50KHBhc3RlMChyb3ckbmFtZSwiIG1lZGlhbiBwcmljZTogJCIsZm9ybWF0KG1lZGlhbihscyRwcmljZSksYmlnLm1hcms9IiwiKSkpCn0KYGBgCgojIyBNYXAKCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQpiZ19jb2xvcj0iI2MwYzBjMCIKdGhlbWVfb3B0czwtbGlzdCh0aGVtZShwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICNwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCAjYnVnLCBub3Qgd29ya2luZwogICAgICAgICAgICAgICAgICAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gYmdfY29sb3IpLAogICAgICAgICAgICAgICAgICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9IGJnX2NvbG9yLCBjb2xvdXIgPSBOQSksCiAgICAgICAgICAgICAgICAgICAgICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGw9YmdfY29sb3IsIHNpemU9MSxsaW5ldHlwZT0ic29saWQiKSwKICAgICAgICAgICAgICAgICAgICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpKSkKYGBgCgoKCgpgYGB7ciBwcmljZV9tYXAsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShzZikKbGlicmFyeShnZ3Bsb3QyKQoKZ2VvPWdldF9jZW5zdXMoZGF0YXNldCA9ICdDQTE2JyxyZWdpb25zPWxpc3QoQ01BPSI1OTkzMyIpLGdlb19mb3JtYXQ9J3NmJyxsZXZlbD0iUmVnaW9ucyIpCgpscyA8LSBnZXRfbGlzdGluZ3MoIjIwMTctMDgtMDEiLCIyMDE3LTA5LTAxIixnZW8kZ2VvbWV0cnksYmVkcz1jKDEpLGZpbHRlciA9ICd1bmZ1cm5pc2hlZCcpCiAgc3VtbWFyeT1scyAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgc2VsZWN0KCJwcmljZSIsImJlZHMiKSAlPiUgZ3JvdXBfYnkoYmVkcykgJT4lIHN1bW1hcml6ZShtZWRpYW49cGFzdGUwKCIkIixmb3JtYXQobWVkaWFuKHByaWNlKSxiaWcubWFyaz0iLCIpKSkgJT4lIG11dGF0ZShuYW1lPXJvdyRuYW1lKQoKY3RzPWdldF9jZW5zdXMoZGF0YXNldCA9ICdDQTE2JyxyZWdpb25zPWxpc3QoQ01BPSI1OTkzMyIpLGdlb19mb3JtYXQ9J3NmJyxsZXZlbD0iQ1NEIikKCm1pbl9saXN0aW5ncz0xMAoKbWVkaWFuX3JlbnQgPC0gZnVuY3Rpb24odil7CiAgcmVzdWx0IDwtIGlmZWxzZShsZW5ndGgodik+bWluX2xpc3RpbmdzLCBtZWRpYW4odiksTkEpCiAgcmV0dXJuKHJlc3VsdCkKfQoKYWdncmVnYXRlX2xpc3RpbmdzIDwtIGFnZ3JlZ2F0ZShjdHMgJT4lIHNlbGVjdCgiR2VvVUlEIiksbHMsZnVuY3Rpb24oeCl7eH0pCgpkYXRhIDwtIGFnZ3JlZ2F0ZShscyAlPiUgc2VsZWN0KCJwcmljZSIpLGN0cyxtZWRpYW5fcmVudCkKCgpjdXRvZmZzPWFzLmludGVnZXIocXVhbnRpbGUoZGF0YSRwcmljZSwgcHJvYnM9c2VxKDAsMSwwLjEpLCBuYS5ybT1UUlVFKSkKbGFiZWxzPWZhY3Rvcihhcy5jaGFyYWN0ZXIoc2VxKDEsbGVuZ3RoKGN1dG9mZnMpLTEpICU+JSBsYXBwbHkoZnVuY3Rpb24oaSl7cmV0dXJuKHBhc3RlMChjdXRvZmZzW2ldLCIgLSAiLGN1dG9mZnNbaSsxXSkpfSkpLG9yZGVyPVRSVUUpCmNvbG9ycz1zZXROYW1lcyhSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwobGVuZ3RoKGxhYmVscyksIlJkWWxCdSIpLGxhYmVscykKZGF0YSRkaXNjcmV0ZV9wcmljZT0gZGF0YSRwcmljZSAlPiUgY3V0KGJyZWFrcz1jdXRvZmZzLCBsYWJlbHM9bGFiZWxzKQoKCmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGE9Y3RzLCBmaWxsPSIjODA4MDgwIiwgc2l6ZT0wLjEpICsKICBnZW9tX3NmKGRhdGE9ZGF0YSwgYWVzKGZpbGwgPSBkaXNjcmV0ZV9wcmljZSksIHNpemU9MC4xKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0nUmRZbEJ1JywgZGlyZWN0aW9uPS0xLCBuYS52YWx1ZT0iIzgwODA4MCIsbmFtZT0iTWVkaWFuIFByaWNlIikgKwogIGxhYnModGl0bGU9IkF1Z3VzdCBTdHVkaW8gYW5kIDEgQmVkcm9vbSBVbmZ1cm5pc2hlZCBNZWRpYW4gQXNrIikgKwogIHRoZW1lX29wdHMKYGBgCgoKCiMjIFJlbnQgZGlzdHJpYnV0aW9ucyBieSBtdW5pY2lwYWxpdHkKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGdnYmVlc3dhcm0pCmxpYnJhcnkoZ3JpZEV4dHJhKQoKcmVnaW9ucz1hc19jZW5zdXNfcmVnaW9uX2xpc3Qoc2VhcmNoX2NlbnN1c19yZWdpb25zKCJWYW5jb3V2ZXIiLCdDQTE2JywnQ01BJykpCgpnZW89Z2V0X2NlbnN1cyhkYXRhc2V0ID0gJ0NBMTYnLHJlZ2lvbnM9cmVnaW9ucyxnZW9fZm9ybWF0PSdzZicsbGV2ZWw9IlJlZ2lvbnMiKQoKbHMgPC0gZ2V0X2xpc3RpbmdzKCIyMDE3LTA4LTAxIiwiMjAxNy0wOS0wMSIsZ2VvJGdlb21ldHJ5LGJlZHM9YygxKSxmaWx0ZXIgPSAndW5mdXJuaXNoZWQnLHNhbml0eT1jKDQwMCw0MDAwKSkKICBzdW1tYXJ5PWxzICU+JSBhcy5kYXRhLmZyYW1lICU+JSBzZWxlY3QoInByaWNlIiwiYmVkcyIpICU+JSBncm91cF9ieShiZWRzKSAlPiUgc3VtbWFyaXplKG1lZGlhbj1wYXN0ZTAoIiQiLGZvcm1hdChtZWRpYW4ocHJpY2UpLGJpZy5tYXJrPSIsIikpKSAlPiUgbXV0YXRlKG5hbWU9cm93JG5hbWUpCgogIApnZW9zPWdldF9jZW5zdXMoZGF0YXNldCA9ICdDQTE2JyxyZWdpb25zPXJlZ2lvbnMsZ2VvX2Zvcm1hdD0nc2YnLGxldmVsPSJDU0QiKSAlPiUKICBzdF9qb2luKGxzKSAKCnRvcF9tdW5pcyA8LSBnZW9zICU+JSBncm91cF9ieShuYW1lKSAlPiUgc3VtbWFyaXplKGNvdW50PWxlbmd0aChuYW1lKSkgJT4lIAogIHRvcF9uKDUsY291bnQpICU+JSBwdWxsKCJuYW1lIikKCnBsb3RfZGF0YSA8LSBnZW9zICU+JSBmaWx0ZXIobmFtZSAlaW4lIHRvcF9tdW5pcykgJT4lCiAgcmVuYW1lKE11bmljaXBhbGl0eT1uYW1lKQp0aXRsZT0iRGlzdHJpYnV0aW9uIG9mIFVuZnVybmlzaGVkIDFiciBSZW50cywgQXVndXN0IDIwMTciCnAxIDwtIGdncGxvdChwbG90X2RhdGEpICsgCiAgZ2VvbV9kZW5zaXR5KGFlcyh4PXByaWNlLCBjb2xvcj1NdW5pY2lwYWxpdHkpKSArCiAgbGFicyh0aXRsZT10aXRsZSkKcDIgPC0gZ2dwbG90KHBsb3RfZGF0YSwgYWVzKE11bmljaXBhbGl0eSwgcHJpY2UpKSsgCiAgZ2VvbV92aW9saW4oYWVzKGZpbGw9TXVuaWNpcGFsaXR5ICkpICsgCiAgI2dlb21fYmVlc3dhcm0ocGNoID0gMSwgY29sPSd3aGl0ZScsIGNleD0wLjgsIGFscGhhPTAuNikgKwogIGxhYnModGl0bGU9dGl0bGUpCmdyaWQuYXJyYW5nZShwMSwgcDIsIG5jb2w9MSkKYGBgCgoKCiMjIExvb2tpbmcgaW50byBDb3F1aXRsYW0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnJlZ2lvbl9uYW1lPSJDb3F1aXRsYW0iICMiUmljaG1vbmQiCnJlZ2lvbnM9YXNfY2Vuc3VzX3JlZ2lvbl9saXN0KHNlYXJjaF9jZW5zdXNfcmVnaW9ucyhyZWdpb25fbmFtZSwnQ0ExNicsJ0NTRCcpICU+JSBmaWx0ZXIobmFtZT09cmVnaW9uX25hbWUpKQoKZ2VvPWdldF9jZW5zdXMoZGF0YXNldCA9ICdDQTE2JyxyZWdpb25zPXJlZ2lvbnMsZ2VvX2Zvcm1hdD0nc2YnLGxldmVsPSJSZWdpb25zIikKCmxzIDwtIGdldF9saXN0aW5ncygiMjAxNy0wOC0wMSIsIjIwMTctMDktMDEiLGdlbyRnZW9tZXRyeSxiZWRzPWMoMSksZmlsdGVyID0gJ3VuZnVybmlzaGVkJyxzYW5pdHk9Yyg0MDAsNDAwMCkpCnN1bW1hcnk9bHMgJT4lIGFzLmRhdGEuZnJhbWUgJT4lIAogIHNlbGVjdCgicHJpY2UiLCJiZWRzIikgJT4lIAogIGdyb3VwX2J5KGJlZHMpICU+JQogIHN1bW1hcml6ZShtZWRpYW49cGFzdGUwKCIkIixmb3JtYXQobWVkaWFuKHByaWNlKSxiaWcubWFyaz0iLCIpKSkgJT4lIAogIG11dGF0ZShuYW1lPXJvdyRuYW1lKQoKY3V0b2Zmcz1jKDQwMCwxMzUwLDQwMDApCmxhYmVscz1mYWN0b3IoYXMuY2hhcmFjdGVyKHNlcSgxLGxlbmd0aChjdXRvZmZzKS0xKSAlPiUgbGFwcGx5KGZ1bmN0aW9uKGkpe3JldHVybihwYXN0ZTAoY3V0b2Zmc1tpXSwiIC0gIixjdXRvZmZzW2krMV0pKX0pKSxvcmRlcj1UUlVFKQpjb2xvcnM9c2V0TmFtZXMoYygidHVycXVvaXNlIiwicHVycGxlIiksbGFiZWxzKQpscyRkaXNjcmV0ZV9wcmljZT0gbHMkcHJpY2UgJT4lIGN1dChicmVha3M9Y3V0b2ZmcywgbGFiZWxzPWxhYmVscykKCiNscyA8LSBjYmluZChscyxzdF9jb29yZGluYXRlcyhzdF90cmFuc2Zvcm0obHMsMTAyMDAyKSRsb2NhdGlvbikpCmxzIDwtIGNiaW5kKGxzLHN0X2Nvb3JkaW5hdGVzKGxzJGxvY2F0aW9uKSkKCmxpYnJhcnkoZ2dtYXApCgpgYGAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpiYXNlIDwtIGdldF9tYXAocGFzdGUwKHJlZ2lvbl9uYW1lLCIsIENhbmFkYSIpLCB6b29tPTEyLCBzb3VyY2UgPSAic3RhbWVuIiwgbWFwdHlwZSA9ICJ0b25lciIsIAogICAgY3JvcCA9IFQpCgojZ2dwbG90KCkgKwogIGdnbWFwKGJhc2UpICsKICAjZ2VvbV9zZihkYXRhPWdlbywgZmlsbD0iIzgwODA4MCIsIHNpemU9MC4xKSArCiAgI2Nvb3JkX3NmKGNycz1zdF9jcnMoMTAyMDAyKSkgKwogIGdlb21fcG9pbnQoZGF0YT1scyAsIGFlcyhjb2xvciA9IGRpc2NyZXRlX3ByaWNlLCB4PVgsIHk9WSksIHNoYXBlPTIxLCBzaXplPTIpICsKICBzY2FsZV9maWxsX21hbnVhbChwYWxldHRlPWNvbG9ycykgKwogIGxhYnModGl0bGU9IkF1Z3VzdCBTdHVkaW8gYW5kIDEgQmVkcm9vbSBVbmZ1cm5pc2hlZCBNZWRpYW4gQXNrIixjb2xvcj0iUHJpY2UiKSArCiAgdGhlbWVfb3B0cwpgYGAKCiMjIFJlbnQgZGlzdHJpYnV0aW9ucyBvdmVyIHRpbWUKCmBgYHtyLCBmaWcuaGVpZ2h0PTcsIGZpZy53aWR0aD03LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKcmVnaW9uX25hbWU9IlZhbmNvdXZlciIgCnJlZ2lvbnM9YXNfY2Vuc3VzX3JlZ2lvbl9saXN0KHNlYXJjaF9jZW5zdXNfcmVnaW9ucyhyZWdpb25fbmFtZSwnQ0ExNicsJ0NTRCcpICU+JSBmaWx0ZXIobmFtZT09cmVnaW9uX25hbWUpKQoKZ2VvPWdldF9jZW5zdXMoZGF0YXNldCA9ICdDQTE2JyxyZWdpb25zPXJlZ2lvbnMsZ2VvX2Zvcm1hdD0nc2YnLGxldmVsPSJSZWdpb25zIikKCmxzIDwtIGdldF9saXN0aW5ncygiMjAxNy0wNS0wMSIsIjIwMTctMDktMDEiLGdlbyRnZW9tZXRyeSxiZWRzPWMoMSksZmlsdGVyID0gJ3VuZnVybmlzaGVkJyxzYW5pdHk9Yyg0MDAsNDAwMCkpCgpscyR5ZWFyX21vbnRoIDwtIGZhY3RvcihzdWJzdHIobHMkcG9zdF9kYXRlLDAsNyksb3JkZXJlZCA9IFRSVUUpCiNscyR5ZWFyX21vbnRoX2RheSA8LSBmYWN0b3Ioc3Vic3RyKGxzJHBvc3RfZGF0ZSwwLDEwKSxvcmRlcmVkID0gVFJVRSkKIAojbHMgJT4lIGdyb3VwX2J5KHllYXJfbW9udGgpICU+JSBzdW1tYXJpemUoY291bnQ9bGVuZ3RoKHllYXJfbW9udGgpKSAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgc2VsZWN0KCJ5ZWFyX21vbnRoIiwiY291bnQiKQojbHMgJT4lIGdyb3VwX2J5KHllYXJfbW9udGhfZGF5KSAlPiUgc3VtbWFyaXplKGNvdW50PWxlbmd0aCh5ZWFyX21vbnRoX2RheSkpICU+JSBhcy5kYXRhLmZyYW1lICU+JSBzZWxlY3QoInllYXJfbW9udGhfZGF5IiwiY291bnQiKQogIAogCnBsb3RfZGF0YSA8LSBscyAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgc2VsZWN0KCJwcmljZSIsInllYXJfbW9udGgiKQp0aXRsZT0iRGlzdHJpYnV0aW9uIG9mIFVuZnVybmlzaGVkIDFiciBSZW50cywgQ2l0eSBvZiBWYW5jb3V2ZXIiCnAxIDwtIGdncGxvdChwbG90X2RhdGEpICsgCiAgZ2VvbV9kZW5zaXR5KGFlcyh4PXByaWNlLCBjb2xvcj15ZWFyX21vbnRoKSkgKwogIGxhYnModGl0bGU9dGl0bGUsIGNvbG9yPSJZZWFyLU1vbnRoIikKcDIgPC0gZ2dwbG90KHBsb3RfZGF0YSwgYWVzKHllYXJfbW9udGgsIHByaWNlKSkrIAogIGdlb21fdmlvbGluKGFlcyhmaWxsPXllYXJfbW9udGggKSkgKyAKICAjZ2VvbV9iZWVzd2FybShwY2ggPSAxLCBjb2w9J3doaXRlJywgY2V4PTAuOCwgYWxwaGE9MC42KSArCiAgbGFicyh0aXRsZT10aXRsZSwgZmlsbD0iWWVhci1Nb250aCIsIHg9IlllYXItTW9udGgiKQpncmlkLmFycmFuZ2UocDEsIHAyLCBuY29sPTEpCmBgYAoKCgojIyBSZW50IGRpc3RyaWJ1dGlvbnMgYnkgbXVuaWNpcGFsaXR5CgpgYGB7ciwgZmlnLmhlaWdodD03LCBmaWcud2lkdGg9NywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShnZ2JlZXN3YXJtKQpsaWJyYXJ5KGdyaWRFeHRyYSkKCnJlZ2lvbl9uYW1lcz1jKCJWYW5jb3V2ZXIiLCJUb3JvbnRvIiwiVmljdG9yaWEiLCJDYWxnYXJ5IikKcmVnaW9ucz0gYXNfY2Vuc3VzX3JlZ2lvbl9saXN0KGRvLmNhbGwocmJpbmQsbGFwcGx5KHJlZ2lvbl9uYW1lcyxmdW5jdGlvbihyZWdpb25fbmFtZSl7cmV0dXJuKChzZWFyY2hfY2Vuc3VzX3JlZ2lvbnMocmVnaW9uX25hbWUsJ0NBMTYnLCdDU0QnKSAlPiUgZmlsdGVyKG5hbWU9PXJlZ2lvbl9uYW1lKSkpfSkpKQoKZ2VvPWdldF9jZW5zdXMoZGF0YXNldCA9ICdDQTE2JyxyZWdpb25zPXJlZ2lvbnMsZ2VvX2Zvcm1hdD0nc2YnLGxldmVsPSJSZWdpb25zIikKCmdlb21ldHJ5PXN0X3VuaW9uKGdlbyRnZW9tZXRyeSkKCmJlZHM9MgpscyA8LSBnZXRfbGlzdGluZ3MoIjIwMTctMDgtMDEiLCIyMDE3LTA5LTAxIixnZW9tZXRyeSxiZWRzPWMoYmVkcyksZmlsdGVyID0gJ3VuZnVybmlzaGVkJyxzYW5pdHk9Yyg0MDAsNTAwMCkpCiAgc3VtbWFyeT1scyAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgc2VsZWN0KCJwcmljZSIsImJlZHMiKSAlPiUgZ3JvdXBfYnkoYmVkcykgJT4lIHN1bW1hcml6ZShtZWRpYW49cGFzdGUwKCIkIixmb3JtYXQobWVkaWFuKHByaWNlKSxiaWcubWFyaz0iLCIpKSkgJT4lIG11dGF0ZShuYW1lPXJvdyRuYW1lKQoKICAKZ2Vvcz1nZXRfY2Vuc3VzKGRhdGFzZXQgPSAnQ0ExNicscmVnaW9ucz1yZWdpb25zLGdlb19mb3JtYXQ9J3NmJyxsZXZlbD0iQ1NEIikgJT4lCiAgc3Rfam9pbihscykgCgoKcGxvdF9kYXRhIDwtIGdlb3MgJT4lIGFzLmRhdGEuZnJhbWUgJT4lIHNlbGVjdCgibmFtZSIsInByaWNlIikgJT4lCiAgcmVuYW1lKE11bmljaXBhbGl0eT1uYW1lKQp0aXRsZT1wYXN0ZTAoIkRpc3RyaWJ1dGlvbiBvZiBVbmZ1cm5pc2hlZCAiLGJlZHMsImJyIFJlbnRzLCBBdWd1c3QgMjAxNyIpCnAxIDwtIGdncGxvdChwbG90X2RhdGEpICsgCiAgZ2VvbV9kZW5zaXR5KGFlcyh4PXByaWNlLCBjb2xvcj1NdW5pY2lwYWxpdHkpKSArCiAgbGFicyh0aXRsZT10aXRsZSkKcDIgPC0gZ2dwbG90KHBsb3RfZGF0YSwgYWVzKE11bmljaXBhbGl0eSwgcHJpY2UpKSsgCiAgZ2VvbV92aW9saW4oYWVzKGZpbGw9TXVuaWNpcGFsaXR5ICkpICsgCiAgI2dlb21fYmVlc3dhcm0ocGNoID0gMSwgY29sPSd3aGl0ZScsIGNleD0wLjgsIGFscGhhPTAuNikgKwogIGxhYnModGl0bGU9dGl0bGUpCmdyaWQuYXJyYW5nZShwMSwgcDIsIG5jb2w9MSkKYGBgCgoKIyMgQ2hlY2tpbmcgU3BlY2lmaWMgYXJlYQoKCmBgYHtyfQpnZW89c2Y6OnJlYWRfc2YoIi4uL2RhdGEvY3VzdG9tX3JlZ2lvbi5nZW9qc29uIikKCmxzIDwtIGdldF9saXN0aW5ncygiMjAxNy0wNi0wMSIsIjIwMTctMDktMDEiLGdlbyRnZW9tZXRyeSxiZWRzPWMoMSksZmlsdGVyID0gJ3VuZnVybmlzaGVkJykKICBzdW1tYXJ5PWxzICU+JSBhcy5kYXRhLmZyYW1lICU+JSBzZWxlY3QoInByaWNlIiwiYmVkcyIpICU+JSBncm91cF9ieShiZWRzKSAlPiUgc3VtbWFyaXplKG1lZGlhbj1wYXN0ZTAoIiQiLGZvcm1hdChtZWRpYW4ocHJpY2UpLGJpZy5tYXJrPSIsIikpKSAlPiUgbXV0YXRlKG5hbWU9cm93JG5hbWUpCgogIGdncGxvdChscywgYWVzKDEsIHByaWNlKSkrIAogIGdlb21fdmlvbGluKGFlcyhmaWxsPTEgKSkgKyAKICAjZ2VvbV9iZWVzd2FybShwY2ggPSAxLCBjb2w9J3doaXRlJywgY2V4PTAuOCwgYWxwaGE9MC42KSArCiAgbGFicyh0aXRsZT0iSnVuZS1BdWd1c3QgMWJyIHVuZnVybmlzaGVkIEN1c3RvbSBSZWdpb24iKQogIApgYGAKCmBgYHtyLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD01fQoKCmN1dG9mZnM9Yyg0MDAsMTA1MCw0MDAwKQpsYWJlbHM9ZmFjdG9yKGFzLmNoYXJhY3RlcihzZXEoMSxsZW5ndGgoY3V0b2ZmcyktMSkgJT4lIGxhcHBseShmdW5jdGlvbihpKXtyZXR1cm4ocGFzdGUwKGN1dG9mZnNbaV0sIiAtICIsY3V0b2Zmc1tpKzFdKSl9KSksb3JkZXI9VFJVRSkKY29sb3JzPXNldE5hbWVzKGMoInR1cnF1b2lzZSIsInB1cnBsZSIpLGxhYmVscykKbHMkZGlzY3JldGVfcHJpY2U9IGxzJHByaWNlICU+JSBjdXQoYnJlYWtzPWN1dG9mZnMsIGxhYmVscz1sYWJlbHMpCmxzIDwtIGNiaW5kKGxzLHN0X2Nvb3JkaW5hdGVzKGxzJGxvY2F0aW9uKSkKCmJhc2UgPC0gZ2V0X21hcChsb2NhdGlvbj1jKC0xMjIuODQ1OTQ1MzU4Mjc2MzcsIDQ5LjE4NDIyODAxNjE2ODE4KSwgem9vbT0xNSwgc291cmNlID0gInN0YW1lbiIsIG1hcHR5cGUgPSAidG9uZXIiLCAKICAgIGNyb3AgPSBUKQoKI2dncGxvdCgpICsKICBnZ21hcChiYXNlKSArCiAgI2dlb21fc2YoZGF0YT1nZW8sIGZpbGw9IiM4MDgwODAiLCBzaXplPTAuMSkgKwogICNjb29yZF9zZihjcnM9c3RfY3JzKDEwMjAwMikpICsKICBnZW9tX3BvaW50KGRhdGE9bHMgLCBhZXMoY29sb3IgPSBkaXNjcmV0ZV9wcmljZSwgeD1YLCB5PVkpLCBzaGFwZT0yMSwgc2l6ZT00KSArCiAgc2NhbGVfZmlsbF9tYW51YWwocGFsZXR0ZT1jb2xvcnMpICsKICBsYWJzKHRpdGxlPSJBdWd1c3QgMSBCZWRyb29tIFVuZnVybmlzaGVkIE1lZGlhbiBBc2siLGNvbG9yPSJQcmljZSIpICsKICB0aGVtZV9vcHRzCgpgYGAKCgoKCgoKCg==